package com.sdk.com.debugserver;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

public class DebugServer {

    public static void main(String[] args) throws IOException {
        new DebugServer().start(19892, 19087);

        System.in.read();
    }

    private static void clearConsole() {
        try {
            String os = System.getProperty("os.name");

            if (os.contains("Windows")) {
                Runtime.getRuntime().exec("cls");
            } else {
                Runtime.getRuntime().exec("clear");
            }
        } catch (Exception exception) {
            //  Handle exception.
        }
    }

    public class DebugApplication {
        private Socket _socket;
        private DataInputStream _input;
        private Map<String, Object> _info;
        private int _clientPort = -1;
        private DebugClientServer debugClientServer;

        public DebugApplication(Socket socket) throws IOException {
            _socket = socket;
            _info = readInfo();
        }

        private Map<String, Object> readInfo() throws IOException {
            _input = new DataInputStream(_socket.getInputStream());

            int size = _input.readInt();

            byte[] data = new byte[size];
            _input.read(data);

            String info = new String(data);

            JSONObject jsonObject = JSON.parseObject(info);

            Map<String, Object> map = new HashMap<>();

            for (String key : jsonObject.keySet()) {
                map.put(key, jsonObject.get(key));
            }

            return map;
        }

        public <T> T parameter(String name, T def) {
            if (_info.containsKey(name)) {
                return (T) _info.get(name);
            }

            return def;
        }

        public String name() {
            return parameter("name", "none");
        }

        public String address() {
            return _socket.getRemoteSocketAddress().toString();
        }

        public String id() {
            return name() + "[" + address() + "]";
        }

        public void relate(final DebugClient client) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        IOManager.write(input(), client.output());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        IOManager.write(client.input(), output());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

        public InputStream input() throws IOException {
            return _socket.getInputStream();
        }

        public OutputStream output() throws IOException {
            return _socket.getOutputStream();
        }

        public void waitClient() {
            debugClientServer = new DebugClientServer() {
                @Override
                public void onAccept(DebugClient client) {
                    try {
                        log("notify app");
                        output().write(151);
                        output().flush();

                        relate(client);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public boolean isContinue(DebugClient client) {
                    return false;
                }
            };
            _clientPort = debugClientServer.startClientServer(10000, 30000);
        }

        public int watchPort() {
            return _clientPort;
        }

        @Override
        public String toString() {
            return id() + ":" + (watchPort() != -1 ? watchPort() + "" : "");
        }

        public String help() {
            try {
                return "use " + debugClientServer.getLocalAddress() + " connect";
            } catch (Exception e) {
                return "";
            }
        }
    }

    public class DebugClientServer {
        private ServerSocket _clientServer;

        public int startClientServer(int start, int end) {
            for (int i = start; i <= end; i++) {
                try {
                    startClientServer(i);
                    return i;
                } catch (Exception e) {

                }
            }

            return -1;
        }

        public String getLocalAddress() throws Exception {
            return getLocalHostLANAddress().getHostAddress() + ":" + _clientServer.getLocalPort();
        }

        public InetAddress getLocalHostLANAddress() throws Exception {
            try {
                InetAddress candidateAddress = null;
                // 遍历所有的网络接口
                for (Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); ifaces.hasMoreElements(); ) {
                    NetworkInterface iface = (NetworkInterface) ifaces.nextElement();
                    // 在所有的接口下再遍历IP
                    for (Enumeration inetAddrs = iface.getInetAddresses(); inetAddrs.hasMoreElements(); ) {
                        InetAddress inetAddr = (InetAddress) inetAddrs.nextElement();
                        if (!inetAddr.isLoopbackAddress() && inetAddr instanceof Inet4Address) {// 排除loopback类型地址
                            if (inetAddr.isSiteLocalAddress()) {
                                // 如果是site-local地址，就是它了
                                return inetAddr;
                            } else if (candidateAddress == null) {
                                // site-local类型的地址未被发现，先记录候选地址
                                candidateAddress = inetAddr;
                            }
                        }
                    }
                }
                if (candidateAddress != null) {
                    return candidateAddress;
                }
                // 如果没有发现 non-loopback地址.只能用最次选的方案
                InetAddress jdkSuppliedAddress = InetAddress.getLocalHost();
                return jdkSuppliedAddress;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        public void startClientServer(int port) throws IOException {
            _clientServer = new ServerSocket(port);

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while (true) {
                            final DebugClient client = acceptClient();

                            new Thread(new Runnable() {
                                @Override
                                public void run() {
                                    onAccept(client);
                                }
                            }).start();

                            if (!isContinue(client)) {
                                break;
                            }
                        }
                    } catch (Exception e) {
                        onError(e);
                    }
                }
            }).start();
        }

        public boolean isContinue(DebugClient client) {
            return true;
        }

        public void onAccept(DebugClient client) {

        }

        private DebugClient acceptClient() throws IOException {
            Socket socket = _clientServer.accept();

            return new DebugClient(socket);
        }
    }

    public class DebugClient {
        private Socket _socket;

        public DebugClient(Socket socket) {
            _socket = socket;
        }

        public InputStream input() throws IOException {
            return _socket.getInputStream();
        }

        public OutputStream output() throws IOException {
            return _socket.getOutputStream();
        }

        public void relate(final DebugApplication app) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        IOManager.write(input(), app.output());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        IOManager.write(app.input(), app.output());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

    private ServerSocket _server;

    private Map<String, DebugApplication> _apps = new HashMap<>();

    public void start(int appPort, int port) throws IOException {
        startApplicationServer(appPort);
    }

    private void startApplicationServer(int port) throws IOException {
        _server = new ServerSocket(port);

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    while (true) {
                        final DebugApplication application = acceptApplication();

                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                onAccept(application);
                            }
                        }).start();
                    }
                } catch (IOException e) {
                    onError(e);
                }

            }
        }).start();
    }

    private void onError(Throwable e) {
        e.printStackTrace();
    }

    private void onAccept(DebugApplication application) {
        _apps.put(application.id(), application);

        if (application.parameter("server", false)) {
            application.waitClient();
        }

        log(application + " online");
        log(application.help());
    }

    private DebugApplication acceptApplication() throws IOException {
        Socket socket = _server.accept();

        return new DebugApplication(socket);
    }

    private void log(String msg) {
        System.out.println(msg);
    }
}
